package com.haruhi.ws.server;

import com.haruhi.event.Message;
import com.haruhi.event.MessageQueue;
import com.haruhi.event.MessageSource;
import com.haruhi.ws.EndpointAdapter;
import com.haruhi.ws.WsListener;
import io.undertow.Undertow;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
import org.xnio.OptionMap;
import org.xnio.Xnio;
import org.xnio.XnioWorker;

import javax.servlet.ServletException;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpointConfig;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

public class ServerEndpointManager {

    private final Map<Integer,UndertowContainer> undertowMap;
    // <port, <sessionId, UndertowSession>>   使用默认ServerEndpoint 的服务端，使用该变量管理客户端session
    private final Map<Integer,ActiveSessionMap> activeSessions;

    public ServerEndpointManager(Builder builder){
        this.undertowMap = builder.undertowContainerMap;
        this.activeSessions = builder.activeSessions;
    }

    // 踢下线一个客户端
    public void removeSession(int port,String sessionId) throws IOException {
        ActiveSessionMap sessionMap = activeSessions.get(port);
        if(sessionMap != null){
            sessionMap.remove(sessionId);
        }
    }

    // 踢下线一个客户端
    public void removeSession(String sessionId) throws IOException {
        for (Map.Entry<Integer, ActiveSessionMap> entry : activeSessions.entrySet()) {
            ActiveSessionMap value = entry.getValue();
            if(!value.containsKey(sessionId)){
                continue;
            }
            value.remove(sessionId);
        }
    }

    public Map<String,Session> getSessions(int port){
        return activeSessions.get(port);
    }


    public static Builder builder(){
        return new Builder();
    }

    public void start() throws ServletException {
        for (Map.Entry<Integer, UndertowContainer> undertowContainerEntry : undertowMap.entrySet()) {
            UndertowContainer undertowContainer = undertowContainerEntry.getValue();
            undertowContainer.start();
        }
    }

    public void start(int port) throws ServletException {
        UndertowContainer undertowContainer = undertowMap.get(port);
        if(undertowContainer != null){
            undertowContainer.start();
        }
    }

    public void stop() throws ServletException {
        for (Map.Entry<Integer, UndertowContainer> undertowContainerEntry : undertowMap.entrySet()) {
            UndertowContainer undertowContainer = undertowContainerEntry.getValue();
            undertowContainer.stop();
        }
    }

    public void stop(int port) throws ServletException {
        UndertowContainer undertowContainer = undertowMap.get(port);
        if(undertowContainer != null){
            undertowContainer.stop();
        }
    }


    private static class UndertowContainer{

        public UndertowContainer(Undertow.Builder builder, ServerEndpointConfig endpointConfig, DeploymentManager deploymentManager) {
            this.builder = builder;
            this.endpointConfig = endpointConfig;
            this.deploymentManager = deploymentManager;
        }

        private final AtomicBoolean started = new AtomicBoolean(false);
        private Undertow undertow;
        private final Undertow.Builder builder;
        private final ServerEndpointConfig endpointConfig;
        private final DeploymentManager deploymentManager;

        public synchronized void start() throws ServletException {
            if(!started.get()){
                deploymentManager.deploy();
                undertow = builder.setHandler(deploymentManager.start()).build();
                undertow.start();
                started.set(true);
            }
        }

        public synchronized void stop() throws ServletException {
            if(started.get() && undertow != null){
                deploymentManager.stop();
                undertow.stop();
                started.set(false);
            }
        }

    }

    private static class DefaultServerEndpoint implements WsListener {

        private final Map<String,Session> innerActiveSessions;

        public DefaultServerEndpoint(Map<String,Session> sessions) {
            this.innerActiveSessions = sessions;
        }

        @Override
        public void onOpen(Session session, EndpointConfig endpointConfig) {
            innerActiveSessions.put(session.getId(),session);
        }

        @Override
        public void onMessage(Session session, String msg) {
            // 这里的消息都是从远程客户端传来的 所以source是CLIENT
            MessageQueue.offer(new Message(session,msg, MessageSource.CLIENT));
        }

        @Override
        public void onClose(Session session, CloseReason closeReason) {
            innerActiveSessions.remove(session.getId());
        }

        @Override
        public void onError(Session session, Throwable thr) {
            innerActiveSessions.remove(session.getId());
        }
    }

    public static class Builder{

        private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();

        private final Map<Integer,EndpointParams> map;
        private final Map<Integer,UndertowContainer> undertowContainerMap;
        private final Map<Integer,ActiveSessionMap> activeSessions;
        private String contextPath;

        public Builder() {
            map = new HashMap<>();
            undertowContainerMap = new HashMap<>();
            activeSessions = new ConcurrentHashMap<>();
        }

        private String initContextPath(String contextPath){
            return contextPath == null || "".equals(contextPath.trim()) ? "/" : contextPath.startsWith("/") ? contextPath : "/" + contextPath;
        }

        public Builder setContextPath(String contextPath){
            this.contextPath = contextPath;
            return this;
        }

        public Builder addServerEndpoint(int port, String path){
            map.put(port,new EndpointParams(new DefaultServerEndpoint(newInstanceInnerActiveSessions(port)), null, path));
            return this;
        }
        public Builder addServerEndpoint(int port, String path,HandshakeInterceptor interceptor){
            map.put(port,new EndpointParams(new DefaultServerEndpoint(newInstanceInnerActiveSessions(port)), interceptor, path));
            return this;
        }
        public Builder addServerEndpoint(int port, String path,WsListener wsListener){
            map.put(port,new EndpointParams(wsListener, null, path));
            return this;
        }
        public Builder addServerEndpoint(int port, String path,WsListener wsListener,HandshakeInterceptor interceptor){
            map.put(port,new EndpointParams(wsListener, interceptor, path));
            return this;
        }

        private synchronized Map<String,Session> newInstanceInnerActiveSessions(int port){
            Map<String, Session> sessionMap = activeSessions.get(port);
            if(sessionMap == null){
                // <sessionId,Session>
                ActiveSessionMap newSessionMap = new ActiveSessionMap();
                activeSessions.put(port,newSessionMap);
                return newSessionMap;
            }
            return sessionMap;
        }

        public ServerEndpointManager build() throws IOException{
            for (Map.Entry<Integer, EndpointParams> paramsEntry : map.entrySet()) {
                Integer port = paramsEntry.getKey();
                EndpointParams endpointParams = paramsEntry.getValue();
                String realPath = initContextPath(contextPath) + endpointParams.getPath();
                ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(EndpointAdapter.class, realPath)
                        .configurator(new ServerEndpointConfig.Configurator(){
                            @Override
                            public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
                                T endpointInstance = super.getEndpointInstance(endpointClass);
                                EndpointAdapter endpointAdapter = (EndpointAdapter) endpointInstance;
                                endpointAdapter.setListener(endpointParams.getWsListener());
                                return (T)endpointAdapter;
                            }
                        })
                        .build();


                Xnio xnio = Xnio.getInstance("nio", Undertow.class.getClassLoader());
                XnioWorker xnioWorker = xnio.createWorker(OptionMap.builder().getMap());
                WebSocketDeploymentInfo webSockets = new WebSocketDeploymentInfo()
                        .addEndpoint(endpointConfig)
                        .setWorker(xnioWorker);
                DeploymentManager deploymentManager = Servlets.defaultContainer()
                        .addDeployment(Servlets.deployment()
                                .setClassLoader(Endpoint.class.getClassLoader())
                                .setContextPath("/")
                                .addOuterHandlerChainWrapper(next -> httpServerExchange -> {
                                    HeaderValues connection = httpServerExchange.getRequestHeaders().get(Headers.CONNECTION_STRING);
                                    HeaderValues upgrade = httpServerExchange.getRequestHeaders().get(Headers.UPGRADE_STRING);
                                    String requestPath = httpServerExchange.getRequestPath();
                                    HandshakeInterceptor handshakeInterceptor = endpointParams.getHandshakeInterceptor();
                                    if(handshakeInterceptor == null || upgrade == null || connection == null ||
                                            upgrade.isEmpty() || connection.isEmpty() || !connection.contains(Headers.UPGRADE_STRING) ||
                                            !upgrade.contains("websocket") || !realPath.equals(requestPath)){
                                        next.handleRequest(httpServerExchange);
                                    }else{
                                        if(handshakeInterceptor.handshake(new HttpRequest(httpServerExchange),new HttpResponse(httpServerExchange))){
                                            next.handleRequest(httpServerExchange);
                                        }
                                    }
                                })
                                .setDeploymentName("ws-deployment-" + ATOMIC_INTEGER.incrementAndGet())
                                .addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, webSockets));
                Undertow.Builder builder = Undertow.builder().addHttpListener(port, "localhost");
//                UndertowContainer undertowContainer = new UndertowContainer(builder,endpointConfig,deploymentManager);
                undertowContainerMap.put(port,new UndertowContainer(builder,endpointConfig,deploymentManager));
//                serverEndpointManager.putContainer(port,undertowContainer);
            }
            return new ServerEndpointManager(this);
        }

        private static class EndpointParams{

            private final WsListener wsListener;
            private final HandshakeInterceptor handshakeInterceptor;
            private final String path;

            public EndpointParams(WsListener wsListener, HandshakeInterceptor handshakeInterceptor, String path) {
                this.wsListener = wsListener;
                this.handshakeInterceptor = handshakeInterceptor;
                this.path = path;
            }

            public HandshakeInterceptor getHandshakeInterceptor() {
                return handshakeInterceptor;
            }

            public WsListener getWsListener() {
                return wsListener;
            }

            public String getPath() {
                return path;
            }
        }
    }

    private static class ActiveSessionMap extends ConcurrentHashMap<String,Session>{

        @Override
        public Session remove(Object key) {
            Session session = get(key);
            try {
                session.close();
                return super.remove(key);
            } catch (IOException e) {
                return null;
            }
        }

        @Deprecated
        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }

        @Deprecated
        @Override
        public Session replace(String key, Session value) {
            throw new UnsupportedOperationException();
        }

        @Deprecated
        @Override
        public boolean replace(String key, Session oldValue, Session newValue) {
            throw new UnsupportedOperationException();
        }

        @Deprecated
        @Override
        public void replaceAll(BiFunction<? super String, ? super Session, ? extends Session> function) {
            throw new UnsupportedOperationException();
        }
    }
}
